import tkinter

from tkx import widget_parse
from tkinter import ttk
from tkx.style_parser import StyleParser
from xml import sax
from typing import *


class TkXmlParse(sax.handler.ContentHandler):
    """ TKX 的 xml 解析器
    """

    def __init__(self, window: 'TkxWindow'):
        super().__init__()
        self.window: 'TkxWindow' = window
        self.widget_stack: List[widget_parse.BuildingWidget] = []
        self.characters_stack: List[str] = []
        self.style_parser: StyleParser = StyleParser()

    def startElement(self, name: str, attrs):
        # characters
        self.characters_stack.append('')
        # widgets
        name = name.lower()
        # 最外层
        if not self.widget_stack:
            if name not in 'window':
                raise Exception("最外层节点必须是 window")
            bw = widget_parse.parse_xml_tag(name, self, attrs, self.window)
            self.widget_stack.append(bw)
            self.window.style = ttk.Style(master=bw.widget)
        # 内层
        else:
            bw = widget_parse.parse_xml_tag(name, self, attrs, self.window)
            self.widget_stack.append(bw)

    def endElement(self, name):
        # characters
        cs = self.characters_stack.pop().strip()
        if cs:
            widget_parse.parse_characters(cs, self, self.window)
        # window
        name = name.lower()
        if name == 'window' and len(self.widget_stack) == 1:
            self.window.tk = self.widget_stack[0].widget
            self.widget_stack.remove(self.widget_stack[-1])
            return
        # 其他组件
        last = self.widget_stack[-1]
        if last:
            if last.on_place:
                last.on_place(last)
            if last.place_func:
                parent = self.widget_stack[-2]
                kw = parent.children_place_args.copy()
                kw.update(last.place_args)
                last.place_func(**kw)
        self.widget_stack.remove(last)

    def endDocument(self):
        self.style_parser.apply(self.window.style)

    def characters(self, content):
        self.characters_stack[-1] += content


class TkxWindow:
    """ Tkx 的窗口基类，构造参数需要传入 xml 字符串。
    """

    def __init__(self, xml: str):
        self.tk: tkinter.Tk = None
        self.style: ttk.Style = None
        self.widgets: Dict[str, Any] = {}
        self.vars: Dict[str, tkinter.Variable] = {}
        if xml:
            sax.parseString(xml, TkXmlParse(self))

    def show(self):
        """ 显示窗口，进入 mainloop。
        """
        self.tk.mainloop()

    def create_var(self, name: str, value=0) -> tkinter.Variable:
        """ 创建 tk 变量，可以指定默认值，如果变量存在会抛出异常。
        """
        if name in self.vars:
            raise Exception(f"变量 {name} 已经存在")
        self.vars[name] = tkinter.Variable(value=value)
        return self.vars[name]

    def get_var(self, name: str, default=0) -> tkinter.Variable:
        """ 获取 tk 变量，如果不存在则会根据默认值创建一个新变量。
        """
        if name in self.vars:
            return self.vars[name]
        return self.create_var(name, default)

    def set_value(self, var_name: str, value: Union[int, float, str, bool]):
        """ 设置 tk 变量的值。
        """
        if var_name in self.vars:
            self.vars[var_name].set(value)
        else:
            self.create_var(var_name, value)

    def value(self, var_name: str) -> Any:
        """ 获取一个 tk 变量的值。
        """
        return self.vars[var_name].get()

    def int(self, var_name: str) -> int:
        """ 获取一个 int 类型的 tk 变量的值。
        """
        return int(self.value(var_name))

    def double(self, var_name: str) -> float:
        """ 获取一个 double(float) 类型的 tk 变量的值。
        """
        return float(self.value(var_name))

    def string(self, var_name: str) -> str:
        """ 获取一个 string 类型的 tk 变量的值。
        """
        return str(self.value(var_name))

    def boolean(self, var_name: str) -> bool:
        """ 获取一个 boolean 类型的 tk 变量的值。
        """
        return bool(self.value(var_name))

    def __getattr__(self, item):
        if item in self.widgets:
            return self.widgets[item]
        if item in self.vars:
            return self.vars[item].get()
        raise AttributeError(f"'{self.__class__}' object has no attribute '{item}'")


if __name__ == '__main__':
    xml = """
<?xml version="1.0" encoding="utf-8" ?>
<window geometry="400x200">
    <menubar>
        <menu text="Menu">
            <menu-command text="quit" command="exit_app"/>
        </menu>
    </menubar>
    <button text="abc" command="@self.show_hello('abc')"/>
    <button text="123" command="@self.show_hello('123')"/>
</window>
    """.strip()

    from tkinter import messagebox


    class ExampleWindow(TkxWindow):
        def show_hello(self, name: str):
            messagebox.showinfo('Hello', name)

        def exit_app(self):
            self.tk.destroy()


    ExampleWindow(xml).show()
